package social;
import java.security.*;
import java.util.concurrent.CountDownLatch;

public class Cracker {
	// Array of chars used to produce strings
	public static final char[] CHARS = "abcdefghijklmnopqrstuvwxyz0123456789.,-!".toCharArray();
	private CrackerWorker[] crackerWorkers;
	private long runTime;
	private CountDownLatch latch;
	private String hashcode = null;
	private String password = null;

	public static void main(String[] args) {
		if (args.length==1) {
			//generation mode
			String password = args[0];
			Cracker generator = new Cracker(password);
			String hash = generator.getHash();
			//System.out.println(hash);
		} else if (args.length==3) {
			//cracking mode
			String hash = args[0];
			int maxLen = Integer.parseInt(args[1]);
			int numThreads = Integer.parseInt(args[2]);
			Cracker cracker = new Cracker(hash, maxLen, numThreads);
			String password = cracker.getPassword();
			//System.out.println(password);
			//System.out.println("Done.");
		} else {
			//System.out.println("Invalid arguments: ");
			for (int i=0; i<args.length; i++) {
				System.err.println(String.format("arg[%d] %s", i, args[i]));
			}
		}
	}

	public Cracker(String password) {
		//generation mode
		String hash = generateHash(password);
		hashcode = hash;
	}

	public Cracker(String hash, int maxLen, int numThreads) {
		//cracking mode
		long startTime = System.currentTimeMillis();
		password = generatePassword(hash, maxLen, numThreads);
		long endTime = System.currentTimeMillis();
		runTime = endTime-startTime;
		//System.out.println(String.format("hash:%s\npassword:%s\nruntime:%dms\n", hash, password, runTime));
	}

	public String getHash() {
		return hashcode;
	}

	public String getPassword() {
		return password;
	}

	public long getRuntime() {
		return runTime;
	}
	
	/*
	 Given a byte[] array, produces a hex String,
	 such as "234a6f". with 2 chars for each byte in the array.
	 (provided code)
	 */
	public static String hexToString(byte[] bytes) {
		StringBuffer buff = new StringBuffer();
		for (int i=0; i<bytes.length; i++) {
			int val = bytes[i];
			val = val & 0xff;  // remove higher bits, sign
			if (val<16) buff.append('0'); // leading 0
			buff.append(Integer.toString(val, 16));
		}
		return buff.toString();
	}

	/*
	 Given a string of hex byte values such as "24a26f", creates
	 a byte[] array of those values, one byte value -128..127
	 for each 2 chars.
	 (provided code)
	 */
	public static byte[] hexToArray(String hex) {
		byte[] result = new byte[hex.length()/2];
		for (int i=0; i<hex.length(); i+=2) {
			result[i/2] = (byte) Integer.parseInt(hex.substring(i, i+2), 16);
		}
		return result;
	}

	private  String generateHash(String password) {
		String hash = null;
		try {
			MessageDigest md = MessageDigest.getInstance("SHA");
			byte[] passwordBytes = password.getBytes();
			md.update(passwordBytes);
			byte[] hashBytes = md.digest();
			hash = hexToString(hashBytes);
		} catch (NoSuchAlgorithmException e) {
			System.err.println(e.getMessage());
			System.err.println(e.getStackTrace());
		}

		return hash;
	}

	private String generatePassword(String hash, int maxLen, int numThreads) {
		String password = null;
		crackerWorkers = new CrackerWorker[numThreads];
		
		latch = new CountDownLatch(1);

		int numWorkers = 0;
		int count = 0;
		for (int i=0; i<40; i+=(40/numThreads)) {
			int startIndex = i;
			int endIndex = i + (40/numThreads);

			if (count < 40 % numThreads) {
				endIndex++;
				i++;
				count++;
			}
			endIndex--;
			crackerWorkers[numWorkers] = new CrackerWorker(startIndex, endIndex, hash, maxLen);
			crackerWorkers[numWorkers].start();
			numWorkers++;
		}

		try {
			latch.await();
		} catch (InterruptedException e) {
			System.err.println(e.getMessage());
		}

		for (int i=0; i<numThreads; i++) crackerWorkers[i].interrupt();
		
		try {
			for (int i=0; i<numThreads; i++) crackerWorkers[i].join();
		} catch (InterruptedException ignored) {
			//ignore
		}
		
		for (int i=0; i<numThreads; i++) {
			if(crackerWorkers[i].getPassword() != null) {
				password = crackerWorkers[i].getPassword();
			}
		}
		
		return password;
	}

	private class CrackerWorker extends Thread {

//		ArrayList<Character> charArray;
		String hash;
		int maxLen;
		private String password = null;
		int startIndex;
		int endIndex;

		public CrackerWorker(int startIndex, int endIndex, String hash, int maxLen) {
			//create the array of characters
//			charArray =  new ArrayList<Character>();
			this.hash = hash;
			this.maxLen = maxLen;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
//			charArray.add(CHARS);	
		}

		public String getPassword() {
			return password;
		}

		public void run() {
			//generate all substrings
			password = testSubstrings(hash, maxLen);
			if (password != null) {
				latch.countDown();
			}
		}

		private String testSubstrings(String hash, int maxLen) {
			for (int i=startIndex; i<=endIndex; i++) {
				String substr = "" + CHARS[i];
				String result = testSubstrings(substr, hash, maxLen);
				if (result != null) return result;
			}
			return null;
		}

		private String testSubstrings(String substr, String hash, int maxLen) {
			if (isInterrupted()) {
				return null;
			}

			if (substr.length()>=maxLen) {
				if (checkHash(hash, substr)) {
					return substr;
				} else {
					return null;
				}
			} else {
				for (int i=0; i<CHARS.length; i++) {
					String substrCopy = substr;
					substrCopy += CHARS[i];

					boolean valid = checkHash(hash, substr);
					if (valid) {
						return substrCopy;
					} else {
						String result = testSubstrings(substrCopy, hash, maxLen);
						if (result != null) {
							return result;
						}
					}
				}
			}

			return null;
		}

		private boolean checkHash(String hash, String password) {
			String passwordHash = generateHash(password);


			if (hash.equals(passwordHash)) {
				return true;
			} else {
				return false;
			}
		}
	}

	// possible test values:
	// a 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8
	// fm adeb6f2a18fe33af368d91b09587b68e3abcb9a7
	// a! 34800e15707fae815d7c90d49de44aca97e2d759
	// xyz 66b27417d37e024c46526c2f6d358a754fc552f3

}
